#include "std/debug.h"
#include "arch/cpu.h"
#include "arch/inout.h"
#include "arch/traps.h"
#include "kernel/irq.h"
#include "kernel/spinlock.h"

// this contains the irq mask for both 8259A irq controllers
static uint cached_irq_mask = 0xFFFB;

DECLARE_PRIVATE_SPINLOCK(i8259A_lock);

#define __byte(x,y) 	(((unsigned char *)&(y))[x])
#define cached_21	(__byte(0,cached_irq_mask))
#define cached_A1	(__byte(1,cached_irq_mask))

static void enable_8259A_irq(uint irq)
{
	unsigned int mask = ~(1 << irq);
	unsigned long flags;

	spin_lock_irqsave(&i8259A_lock, flags);
	cached_irq_mask &= mask;
	if (irq & 8)
		outb(cached_A1,0xA1);
	else
		outb(cached_21,0x21);
	spin_unlock_irqrestore(&i8259A_lock, flags);
}

static void disable_8259A_irq(uint irq)
{
	unsigned int mask = 1 << irq;
	unsigned long flags;

	spin_lock_irqsave(&i8259A_lock, flags);
	cached_irq_mask |= mask;
	if (irq & 8)
		outb(cached_A1,0xA1);
	else
		outb(cached_21,0x21);
	spin_unlock_irqrestore(&i8259A_lock, flags);
}

static uint startup_8259A_irq(uint irq)
{ 
	enable_8259A_irq(irq);
	dbg_output3("8259A_startup at IRQ%u\n", irq);
	return 0; /* never anything pending */
}

#define shutdown_8259A_irq	disable_8259A_irq


static void end_8259A_irq(uint irq)
{
	if (!(irq_desc[irq].status & (IRQ_DISABLED | IRQ_INPROGRESS)))
		enable_8259A_irq(irq);
}

// This has to be protected by the irq controller spinlock
// before being called.
static inline int i8259A_irq_real(uint irq)
{
	int value;
	int irqmask = 1 << irq;

	if (irq < 8) {
		outb(0x0B,0x20);				/* ISR register */
		value = inb(0x20) & irqmask;
		outb(0x0A,0x20);				/* back to the IRR register */
		return value;
	}
	outb(0x0B,0xA0);					/* ISR register */
	value = inb(0xA0) & (irqmask >> 8);
	outb(0x0A,0xA0);					/* back to the IRR register */
	return value;
}

static void mask_and_ack_8259A(unsigned int irq)
{
	unsigned int irqmask = 1 << irq;
	unsigned long flags;

	spin_lock_irqsave(&i8259A_lock, flags);

	if (cached_irq_mask & irqmask)
		goto spurious_8259A_irq;
	cached_irq_mask |= irqmask;

handle_real_irq:
	if (irq & 8) {
		inb(0xA1);					/* DUMMY - (do we need this?) */
		outb(cached_A1,0xA1);
		outb(0x60+(irq&7),0xA0);	/* 'Specific EOI' to slave */
		outb(0x62,0x20);			/* 'Specific EOI' to master-IRQ2 */
	} else {
		inb(0x21);					/* DUMMY - (do we need this?) */
		outb(cached_21,0x21);
		outb(0x60+irq,0x20);		/* 'Specific EOI' to master */
	}
	spin_unlock_irqrestore(&i8259A_lock, flags);
	return;

spurious_8259A_irq:
	
	// this is the slow path - should happen rarely
	if (i8259A_irq_real(irq))
		/*
		 * oops, the IRQ _is_ in service according to the
		 * 8259A - not spurious, go handle it.
		 */
		goto handle_real_irq;

	{
		static int spurious_irq_mask = 0;
		/*
		 * At this point we can be sure the IRQ is spurious,
		 * lets ACK and report it. [once per IRQ]
		 */
		if (!(spurious_irq_mask & irqmask)) {
			dbg_output1("spurious 8259A interrupt: IRQ%d.\n", irq);
			spurious_irq_mask |= irqmask;
		}
		irq_err_count++;
		/*
		 * Theoretically we do not have to handle this IRQ,
		 * but in Linux this does not cause problems and is
		 * simpler for us.
		 */
		goto handle_real_irq;
	}
}

irq_controller_t i8259A_irq_controller =
{
	"XT-PIC",
	startup_8259A_irq,
	shutdown_8259A_irq,
	enable_8259A_irq,
	disable_8259A_irq,
	mask_and_ack_8259A,
	end_8259A_irq,
};

void init_8259A(void)
{
	unsigned long flags;

	spin_lock_irqsave(&i8259A_lock, flags);

	outb(0xff, 0x21);				/* mask all of 8259A-1 */
	outb(0xff, 0xA1);				/* mask all of 8259A-2 */

	// Intel said that 0x00 ~ 0x2F should be reserved

	outb_p(0x11, 0x20);				/* ICW1: select 8259A-1 init */
	outb_p(0x30 + 0, 0x21);			/* ICW2: 8259A-1 IR0-7 mapped to 0x30-0x37 */
	outb_p(0x04, 0x21);				/* 8259A-1 (the master) has a slave on IR2 */
	outb_p(0x01, 0x21);				/* master expects normal EOI */

	outb_p(0x11, 0xA0);				/* ICW1: select 8259A-2 init */
	outb_p(0x30 + 8, 0xA1);			/* ICW2: 8259A-2 IR0-7 mapped to 0x38-0x3f */
	outb_p(0x02, 0xA1);				/* 8259A-2 is a slave on master's IR2 */
	outb_p(0x01, 0xA1);				/* (slave's support for AEOI in flat mode is to be investigated) */

	// udelay(100);					/* wait for 8259A to initialize */
	{ volatile int i; for (i = 0; i < 100; ++i); }

	outb(cached_21, 0x21);			/* restore master IRQ mask */
	outb(cached_A1, 0xA1);			/* restore slave IRQ mask */

	spin_unlock_irqrestore(&i8259A_lock, flags);
}

/* EOF */
